﻿using System;
using System.IO;
using System.Text;
using System.Xml.Serialization;
using SlamCms.Common;
using SlamCms.Configuration;

namespace SlamCms.SharePointServices
{
	public abstract class SharePointServiceBase
	{
		protected abstract string FolderVirtualPath { get; }

		protected virtual bool MustDownloadFile(string localFilePath)
		{
			var fileMetadata = GetFileMetadata(localFilePath);

			if (fileMetadata != null && fileMetadata.IsDirty)
				return true;

			if (fileMetadata != null && fileMetadata.NotAvailable && DateTime.UtcNow.Subtract(fileMetadata.LastModified).TotalMinutes < 5)
				return false;

			if (File.Exists(localFilePath))
				return false;

			return true;
		}

		protected virtual FileData RetrieveFileFromFileSystem(string localFilePath)
		{
			var fileMetadata = GetFileMetadata(localFilePath);

			if (!File.Exists(localFilePath))
			{
				Exception innerException = null;

				if (fileMetadata != null)
				{
					var exceptionMessage = new StringBuilder();
					exceptionMessage.AppendLine("Remote URL: {0}".F(fileMetadata.Url));
					exceptionMessage.AppendLine("StatusDescription: {0}".F(fileMetadata.StatusDescription));
					exceptionMessage.AppendLine("Last tried: {0} ago".F(DateTime.UtcNow.Subtract(fileMetadata.LastModified)));
					innerException = new Exception(exceptionMessage.ToString());
				}

				throw new FileNotFoundException("The local file {0} could not be found".F(localFilePath), innerException);
			}

			var fileInfo = new FileInfo(localFilePath);

			var fileData = new FileData()
			{
				Name = fileInfo.Name,
				Content = File.OpenRead(localFilePath),
				ContentLength = fileInfo.Length,
				LastModified = fileInfo.LastWriteTime
			};

			if (fileMetadata != null)
			{
				fileData.Tag = fileMetadata.Tag;
				fileData.ContentType = fileMetadata.ContentType;
				fileData.LastModified = fileMetadata.LastModified;
			}

			if (fileData.ContentType.IsNullOrEmpty())
				fileData.ContentType = MimeTypeHelpers.GetFromFileName(localFilePath);

			return fileData;
		}

		protected virtual void Download(string url, string localFilePath)
		{
			var folder = Path.GetDirectoryName(localFilePath);

			if (!Directory.Exists(folder))
				Directory.CreateDirectory(folder);

			var downloadedFileData = DocumentLibraryService.GetFileData(url);

			SaveFileMetadata(localFilePath, url, downloadedFileData);

			if (downloadedFileData.MessageCode != ServiceResponseMessageCode.None)
				return;

			using (var downloadedStream = downloadedFileData.Response.Content)
			{
				using (var fileStream = File.Open(localFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
				{
					downloadedStream.CopyTo(fileStream);
					fileStream.Close();
				}

				downloadedStream.Close();
			}
		}

		protected virtual void UpdateMetadata(string localFilePath, Action<FileMetadata> action)
		{
			Ensure.ArgumentNotNull(localFilePath, "localFilePath");
			Ensure.ArgumentNotNull(action, "action");

			var fileMetadata = GetFileMetadata(localFilePath);
			if (fileMetadata == null)
				return;

			action(fileMetadata);

			SaveFileMetadata(localFilePath, fileMetadata);
		}

		protected virtual void SaveFileMetadata(string localFilePath, string url, ServiceResponse<FileData> fileDataServiceResponse)
		{
			var fileMetadata = new FileMetadata();
			fileMetadata.Url = url;

			if (fileDataServiceResponse.MessageCode != ServiceResponseMessageCode.None)
			{
				fileMetadata.NotAvailable = true; 
				fileMetadata.LastModified = DateTime.UtcNow;
				fileMetadata.StatusDescription = fileDataServiceResponse.Message;
			}
			else
			{
				var fileData = fileDataServiceResponse.Response;
				fileMetadata.NotAvailable = false;
				fileMetadata.LastModified = fileData.LastModified;
				fileMetadata.Tag = fileData.Tag;
				fileMetadata.ContentType = fileData.ContentType;
			}

			SaveFileMetadata(localFilePath, fileMetadata);
		}

		protected virtual void SaveFileMetadata(string localFilePath, FileMetadata fileMetadata)
		{
			var metadataFilePath = GetFileMetadataPath(localFilePath);

			using (TextWriter metadataFileStream = new StreamWriter(metadataFilePath))
			{
				var xmlSerializer = new XmlSerializer(fileMetadata.GetType());
				xmlSerializer.Serialize(metadataFileStream, fileMetadata);
				metadataFileStream.Close();
			}
		}

		protected virtual FileMetadata GetFileMetadata(string localFilePath)
		{
			var metadataFilePath = GetFileMetadataPath(localFilePath);
			if (!File.Exists(metadataFilePath))
				return null;

			using (TextReader metadataFileStream = new StreamReader(metadataFilePath))
			{
				var xmlSerializer = new XmlSerializer(typeof(FileMetadata));
				var fileMetadata = (FileMetadata)xmlSerializer.Deserialize(metadataFileStream);
				metadataFileStream.Close();
				return fileMetadata;
			}
		}

		protected virtual string GetFileMetadataPath(string localFilePath)
		{
			return "{0}.metadata".F(localFilePath);
		}

		protected virtual void SaveInternal(string url, Stream file)
		{
			Ensure.ArgumentNotNullOrEmpty(url, "url");
			Ensure.ArgumentNotNull(file, "file");

			var sharePointUrl = new SharePointUrl(url);

			DocumentLibraryService.AddItem(sharePointUrl.Library, sharePointUrl.FileName, file);
		}

		protected virtual void DeleteInternal(string url)
		{
			Ensure.ArgumentNotNullOrEmpty(url, "url");

			var sharePointUrl = new SharePointUrl(url);
			
			DocumentLibraryService.DeleteFile(sharePointUrl.Library, sharePointUrl.FileName);
		}

		//protected virtual void DeleteFiles(string folder, string filesStartingWith)
		//{
		//	if (!Directory.Exists(folder))
		//		return;

		//	foreach (var file in Directory.GetFiles(folder, "{0}*".F(filesStartingWith), SearchOption.TopDirectoryOnly))
		//	{
		//		try
		//		{
		//			File.Delete(file);
		//		}
		//		catch (IOException)
		//		{
		//			// ignore deletion problems
		//		}
		//	}
		//}

		protected static string GetAbsoluteUrl(string relativeOrAbsoluteUrl)
		{
			if (relativeOrAbsoluteUrl.StartsWith("http"))
				return relativeOrAbsoluteUrl;

			return ConfigurationManager.Environment.DefaultApplication.AdminUrl.EnsureEndsWith("/") + relativeOrAbsoluteUrl.TrimStart('/');
		}

		private class SharePointUrl
		{
			public SharePointUrl(string url)
			{
				Url = url;

				// split library and id
				var lastSlashPos = url.LastIndexOf("/", StringComparison.Ordinal);
				var queryStringPos = url.IndexOf("?", StringComparison.Ordinal);
				var hasQueryString = queryStringPos >= 0;
				QueryString = hasQueryString ? url.Substring(queryStringPos + 1) : string.Empty;

				Library = GetAbsoluteUrl(url.Substring(0, lastSlashPos + 1));
				FileName = url.Substring(lastSlashPos + 1, url.Length - lastSlashPos - 1);
				if (hasQueryString)
					FileName = FileName.Substring(0, FileName.IndexOf("?", StringComparison.Ordinal));
			}

			public string FileName { get; private set; }
			public string Library { get; private set; }
			public string Url { get; private set; }
			public string QueryString { get; private set; }
		}
	}
}
